﻿' Adapted from: http://www.codeproject.com/KB/miscctrl/collapsiblesplitter.aspx

Imports System
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms


#Region "Enums"
''' <summary>
''' Enumeration to sepcify the visual style to be applied to the CollapsibleSplitter control
''' </summary>
Public Enum VisualStyles
    Mozilla = 0
    XP
    Win9x
    DoubleDots
    Lines
End Enum

''' <summary>
''' Enumeration to specify the current animation state of the control.
''' </summary>
Public Enum SplitterState
    Collapsed = 0
    Expanding
    Expanded
    Collapsing
End Enum

#End Region

Public Class CollapsibleSplitter
    Inherits System.Windows.Forms.SplitContainer

    Public Sub New()
        MyBase.New()
        If IsDesignMode(Me) Then Exit Sub
        ' set the current state
        If Me.IsCollapsed Then
            Me.currentState = SplitterState.Collapsed
        Else
            Me.currentState = SplitterState.Expanded
        End If
    End Sub

#Region "Public Events"

    ''' <summary>
    ''' Thrown when splitter collapsed state changes
    ''' </summary>
    ''' <param name="IsCollapsed">True if has just been collapsed</param>
    Public Event CollapseChanged(IsCollapsed As Boolean)

#End Region

#Region "Private Properties"

    ' declare and define some base properties
    Private hot As Boolean
    Private hotColor As System.Drawing.Color = CalculateColor(SystemColors.Highlight, SystemColors.Window, 70)
    Private rectBar As System.Drawing.Rectangle
    Private m_visualStyle As VisualStyles

    Private m_ExpandedDistance As Integer
    Private m_MinSize As Integer

    ' animation controls introduced in version 1.22
    Private WithEvents animationTimer As New System.Windows.Forms.Timer
    Private WithEvents toolTip1 As New ToolTip

    Private currentState As SplitterState
    Private m_animationDelay As Integer = 20
    Private m_animationStep As Integer = 20
    Private m_useAnimations As Boolean

    Private Function CollapsedDistance() As Integer
        If Me.Orientation = Windows.Forms.Orientation.Vertical Then
            Return IIf(Me.FixedPanel = Windows.Forms.FixedPanel.Panel1, 0, Me.Width - Me.SplitterWidth)
        Else
            Return IIf(Me.FixedPanel = Windows.Forms.FixedPanel.Panel1, 0, Me.Height - Me.SplitterWidth)
        End If
    End Function

#End Region

#Region "Public Properties"

    ''' <summary>
    ''' The initial state of the Splitter. Set to True if the control to hide is not visible by default
    ''' </summary>
    <Bindable(True), Category("Collapsing Options"), DefaultValue("False"), Description("The initial state of the Splitter. Set to True if the control to hide is not visible by default")>
    Public Property IsCollapsed() As Boolean
        Set(ByVal value As Boolean)
            If value Then
                If Me.CollapsedDistance = 0 Then
                    m_MinSize = Panel1MinSize
                    Panel1MinSize = 0
                Else
                    m_MinSize = Panel2MinSize
                    Panel2MinSize = 0
                End If
                Me.SplitterDistance = IIf(value, CollapsedDistance, m_ExpandedDistance)
                currentState = SplitterState.Collapsed
            Else
                currentState = SplitterState.Expanded
            End If
        End Set
        Get
            Return Me.SplitterDistance = CollapsedDistance()
            'Return currentState = SplitterState.Collapsed
        End Get
    End Property

    ''' <summary>
    ''' The initial size of the expanded Splitter
    ''' </summary>
    <Bindable(True), Category("Collapsing Options"), DefaultValue("100"), Description("The initial expanded size of the Splitter.")>
    Public Property ExpandedDistance() As Integer
        Get
            Return m_ExpandedDistance
        End Get
        Set(ByVal value As Integer)
            m_ExpandedDistance = value
            If Not IsCollapsed Then Me.SplitterDistance = value
        End Set
    End Property

    ''' <summary>
    ''' Determines if the collapse and expanding actions will be animated
    ''' </summary>
    <Bindable(True), Category("Collapsing Options"), DefaultValue("True"), Description("Determines if the collapse and expanding actions will be animated")>
    Public Property UseAnimations() As Boolean
        Get
            Return Me.m_useAnimations
        End Get
        Set(ByVal value As Boolean)
            Me.m_useAnimations = value
        End Set
    End Property

    ''' <summary>
    ''' The delay in millisenconds between animation steps
    ''' </summary>
    <Bindable(True), Category("Collapsing Options"), DefaultValue("20"), Description("The delay in millisenconds between animation steps")>
    Public Property AnimationDelay() As Integer
        Get
            Return Me.m_animationDelay
        End Get
        Set(ByVal value As Integer)
            Me.m_animationDelay = value
            If value <> 0 Then animationTimer.Interval = value
        End Set
    End Property

    ''' <summary>
    ''' The amount of pixels moved in each animation step
    ''' </summary>
    <Bindable(True), Category("Collapsing Options"), DefaultValue("20"), Description("The amount of pixels moved in each animation step")>
    Public Property AnimationStep() As Integer
        Get
            Return Me.m_animationStep
        End Get
        Set(ByVal value As Integer)
            Me.m_animationStep = value
        End Set
    End Property

    ''' <summary>
    ''' The visual style that will be painted on the control
    ''' </summary>
    <Bindable(True), Category("Collapsing Options"), DefaultValue("VisualStyles.XP"), Description("The visual style that will be painted on the control")>
    Public Property VisualStyle() As VisualStyles
        Get
            Return Me.m_visualStyle
        End Get
        Set(ByVal value As VisualStyles)
            Me.m_visualStyle = value
            Me.Invalidate()
        End Set
    End Property

#End Region

#Region "Private Methods"

    Private Sub ToggleSplitter()

        ' if an animation is currently in progress for this control, drop out
        If currentState = SplitterState.Collapsing OrElse currentState = SplitterState.Expanding Then
            Exit Sub
        End If

        If Not Me.IsCollapsed Then
            If Me.CollapsedDistance = 0 Then
                m_MinSize = Panel1MinSize
                Panel1MinSize = 0
            Else
                m_MinSize = Panel2MinSize
                Panel2MinSize = 0
            End If
            If m_useAnimations Then
                currentState = SplitterState.Collapsing
                Me.animationTimer.Enabled = True
            Else
                ' no animations, so just toggle the visible state
                currentState = SplitterState.Collapsed
                Me.SplitterDistance = Me.CollapsedDistance
                toolTip1.SetToolTip(Me, "Click to expand")
                Application.DoEvents()
            End If
        Else
            ' control to hide is collapsed
            If Me.CollapsedDistance = 0 Then
                Me.Panel1MinSize = m_MinSize
            Else
                Me.Panel2MinSize = m_MinSize
            End If
            If m_useAnimations Then
                currentState = SplitterState.Expanding
                Me.SplitterDistance = Me.CollapsedDistance
                Me.animationTimer.Enabled = True
            Else
                ' no animations, so just toggle the visible state
                currentState = SplitterState.Expanded
                Me.SplitterDistance = m_ExpandedDistance
                toolTip1.SetToolTip(Me, "Drag to resize; click blue bar to collapse")
                Application.DoEvents()
            End If
        End If
        Me.Refresh()
        RaiseEvent CollapseChanged(IsCollapsed)
    End Sub

#End Region

#Region "Public Methods"

    Public Sub ToggleState()
        Me.ToggleSplitter()
    End Sub

#End Region

#Region "Arrow Polygon Array"

    ' This creates a point array to draw a arrow-like polygon
    Private Function ArrowPointArray(ByVal x As Integer, ByVal y As Integer) As Point()
        Dim point As Point() = New Point(2) {}

        ' decide which direction the arrow will point
        If Me.Orientation = Windows.Forms.Orientation.Vertical Then
            If Me.IsCollapsed And Me.CollapsedDistance = 0 OrElse Not Me.IsCollapsed And Me.CollapsedDistance <> 0 Then
                ' right arrow
                point(0) = New Point(x, y)
                point(1) = New Point(x + 3, y + 3)
                point(2) = New Point(x, y + 6)
            Else
                ' left arrow
                point(0) = New Point(x + 3, y)
                point(1) = New Point(x, y + 3)
                point(2) = New Point(x + 3, y + 6)
            End If
        Else
            If Me.IsCollapsed And Me.CollapsedDistance = 0 OrElse Not Me.IsCollapsed And Me.CollapsedDistance <> 0 Then
                ' down arrow
                point(0) = New Point(x, y)
                point(1) = New Point(x + 6, y)
                point(2) = New Point(x + 3, y + 3)
            Else
                ' up arrow
                point(0) = New Point(x + 3, y)
                point(1) = New Point(x + 6, y + 4)
                point(2) = New Point(x, y + 4)
            End If
        End If
        Return point
    End Function

#End Region

#Region "Color Calculator"

    ' this method was borrowed from the RichUI Control library by Sajith M
    Private Shared Function CalculateColor(ByVal front As Color, ByVal back As Color, ByVal alpha As Integer) As Color
        ' solid color obtained as a result of alpha-blending

        Dim frontColor As Color = Color.FromArgb(255, front)
        Dim backColor As Color = Color.FromArgb(255, back)

        Dim frontRed As Single = frontColor.R
        Dim frontGreen As Single = frontColor.G
        Dim frontBlue As Single = frontColor.B
        Dim backRed As Single = backColor.R
        Dim backGreen As Single = backColor.G
        Dim backBlue As Single = backColor.B

        Dim fRed As Single = frontRed * alpha / 255 + backRed * (CSng((255 - alpha)) / 255)
        Dim newRed As Byte = CByte(fRed)
        Dim fGreen As Single = frontGreen * alpha / 255 + backGreen * (CSng((255 - alpha)) / 255)
        Dim newGreen As Byte = CByte(fGreen)
        Dim fBlue As Single = frontBlue * alpha / 255 + backBlue * (CSng((255 - alpha)) / 255)
        Dim newBlue As Byte = CByte(fBlue)

        Return Color.FromArgb(255, newRed, newGreen, newBlue)
    End Function

#End Region

#Region "Animation Timer Tick"

    Private Sub animationTimerTick(ByVal sender As Object, ByVal e As System.EventArgs) Handles animationTimer.Tick
        Select Case currentState
            Case SplitterState.Collapsing
                If IIf(Me.CollapsedDistance = 0, Me.SplitterDistance <= Me.CollapsedDistance, Me.SplitterDistance >= Me.CollapsedDistance) Then
                    Me.SplitterDistance = Me.CollapsedDistance
                    animationTimer.Enabled = False
                    currentState = SplitterState.Collapsed
                    Me.Invalidate()
                Else
                    Me.SplitterDistance = Math.Max(0, Math.Min(Me.Width, Me.SplitterDistance + m_animationStep * IIf(Me.CollapsedDistance = 0, -1, 1)))
                End If

            Case SplitterState.Expanding

                If IIf(Me.CollapsedDistance = 0, Me.SplitterDistance >= m_ExpandedDistance, Me.SplitterDistance <= m_ExpandedDistance) Then
                    Me.SplitterDistance = m_ExpandedDistance
                    animationTimer.Enabled = False
                    currentState = SplitterState.Expanded
                    Me.Invalidate()
                Else
                    Me.SplitterDistance = Math.Max(0, Math.Min(Me.Width, Me.SplitterDistance + m_animationStep * IIf(Me.CollapsedDistance = 0, 1, -1)))
                End If
        End Select
        Application.DoEvents()
    End Sub

#End Region

#Region "Event Handlers"

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)

        ' create a Graphics object
        Dim g As Graphics = e.Graphics

        ' find the rectangle for the splitter and paint it
        Dim r As Rectangle = Me.SplitterRectangle
        g.FillRectangle(New SolidBrush(Me.BackColor), r)

        ' Check the docking style and create the control rectangle accordingly

        If Me.Orientation = Windows.Forms.Orientation.Vertical Then
            ' create a new rectangle in the vertical center of the splitter for our collapse control button
            rectBar = New Rectangle(r.X, CInt(r.Y) + ((r.Height - 115) / 2), 8, 115)

            ' force the width to 8px so that everything always draws correctly
            Me.SplitterWidth = 8

            ' draw the background color for our control image
            If hot Then
                If currentState = SplitterState.Collapsed Then 'highlight entire splitter bar
                    g.FillRectangle(New SolidBrush(hotColor), r)
                Else 'highlight only resize bar
                    g.FillRectangle(New SolidBrush(hotColor), rectBar)
                End If
            Else
                g.FillRectangle(New SolidBrush(Me.BackColor), rectBar)
            End If

            ' draw the top & bottom lines for our control image
            g.DrawLine(New Pen(SystemColors.ControlDark, 1), rectBar.X + 1, rectBar.Y, rectBar.X + rectBar.Width - 2, rectBar.Y)
            g.DrawLine(New Pen(SystemColors.ControlDark, 1), rectBar.X + 1, rectBar.Y + rectBar.Height, rectBar.X + rectBar.Width - 2, rectBar.Y + rectBar.Height)

            If Me.Enabled Then
                ' draw the arrows for our control image
                ' the ArrowPointArray is a point array that defines an arrow shaped polygon
                g.FillPolygon(New SolidBrush(SystemColors.ControlDarkDark), ArrowPointArray(rectBar.X + 2, rectBar.Y + 3))
                g.FillPolygon(New SolidBrush(SystemColors.ControlDarkDark), ArrowPointArray(rectBar.X + 2, rectBar.Y + rectBar.Height - 9))
            End If

            ' draw the dots for our control image using a loop
            Dim x As Integer = rectBar.X + 3
            Dim y As Integer = rectBar.Y + 14

            ' Visual Styles added in version 1.1
            Select Case m_visualStyle
                Case VisualStyles.Mozilla

                    For i As Integer = 0 To 29
                        ' light dot
                        g.DrawLine(New Pen(SystemColors.ControlLightLight), x, y + (i * 3), x + 1, y + 1 + (i * 3))
                        ' dark dot
                        g.DrawLine(New Pen(SystemColors.ControlDarkDark), x + 1, y + 1 + (i * 3), x + 2, y + 2 + (i * 3))
                        ' overdraw the background color as we actually drew 2px diagonal lines, not just dots
                        If hot Then
                            g.DrawLine(New Pen(hotColor), x + 2, y + 1 + (i * 3), x + 2, y + 2 + (i * 3))
                        Else
                            g.DrawLine(New Pen(Me.BackColor), x + 2, y + 1 + (i * 3), x + 2, y + 2 + (i * 3))
                        End If
                    Next
                    Exit Select

                Case VisualStyles.DoubleDots
                    For i As Integer = 0 To 29
                        ' light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLightLight), x, y + 1 + (i * 3), 1, 1)
                        ' dark dot
                        g.DrawRectangle(New Pen(SystemColors.ControlDark), x - 1, y + (i * 3), 1, 1)
                        i += 1
                        ' light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLightLight), x + 2, y + 1 + (i * 3), 1, 1)
                        ' dark dot
                        g.DrawRectangle(New Pen(SystemColors.ControlDark), x + 1, y + (i * 3), 1, 1)
                    Next
                    Exit Select

                Case VisualStyles.Win9x

                    g.DrawLine(New Pen(SystemColors.ControlLightLight), x, y, x + 2, y)
                    g.DrawLine(New Pen(SystemColors.ControlLightLight), x, y, x, y + 90)
                    g.DrawLine(New Pen(SystemColors.ControlDark), x + 2, y, x + 2, y + 90)
                    g.DrawLine(New Pen(SystemColors.ControlDark), x, y + 90, x + 2, y + 90)
                    Exit Select

                Case VisualStyles.XP

                    For i As Integer = 0 To 17
                        ' light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLight), x, y + (i * 5), 2, 2)
                        ' light light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLightLight), x + 1, y + 1 + (i * 5), 1, 1)
                        ' dark dark dot
                        g.DrawRectangle(New Pen(SystemColors.ControlDarkDark), x, y + (i * 5), 1, 1)
                        ' dark fill
                        g.DrawLine(New Pen(SystemColors.ControlDark), x, y + (i * 5), x, y + (i * 5) + 1)
                        g.DrawLine(New Pen(SystemColors.ControlDark), x, y + (i * 5), x + 1, y + (i * 5))
                    Next
                    Exit Select

                Case VisualStyles.Lines

                    For i As Integer = 0 To 43
                        g.DrawLine(New Pen(SystemColors.ControlDark), x, y + (i * 2), x + 2, y + (i * 2))
                    Next

                    Exit Select
            End Select

        Else
            ' create a new rectangle in the horizontal center of the splitter for our collapse control button
            rectBar = New Rectangle(CInt(r.X) + ((r.Width - 115) / 2), r.Y, 115, 8)
            ' force the height to 8px
            Me.SplitterWidth = 8

            ' draw the background color for our control image
            If hot Then
                If currentState = SplitterState.Collapsed Then 'highlight entire splitter bar
                    g.FillRectangle(New SolidBrush(hotColor), r)
                Else 'highlight only resize bar
                    g.FillRectangle(New SolidBrush(hotColor), rectBar)
                End If
                'g.FillRectangle(New SolidBrush(hotColor), New Rectangle(rectBar.X, rectBar.Y + 1, 115, 6))
            Else
                g.FillRectangle(New SolidBrush(Me.BackColor), rectBar)
                'g.FillRectangle(New SolidBrush(Me.BackColor), New Rectangle(rectBar.X, rectBar.Y + 1, 115, 6))
            End If

            ' draw the left & right lines for our control image
            g.DrawLine(New Pen(SystemColors.ControlDark, 1), rectBar.X, rectBar.Y + 1, rectBar.X, rectBar.Y + rectBar.Height - 2)
            g.DrawLine(New Pen(SystemColors.ControlDark, 1), rectBar.X + rectBar.Width, rectBar.Y + 1, rectBar.X + rectBar.Width, rectBar.Y + rectBar.Height - 2)

            If Me.Enabled Then
                ' draw the arrows for our control image
                ' the ArrowPointArray is a point array that defines an arrow shaped polygon
                g.FillPolygon(New SolidBrush(SystemColors.ControlDarkDark), ArrowPointArray(rectBar.X + 3, rectBar.Y + 2))
                g.FillPolygon(New SolidBrush(SystemColors.ControlDarkDark), ArrowPointArray(rectBar.X + rectBar.Width - 9, rectBar.Y + 2))
            End If

            ' draw the dots for our control image using a loop
            Dim x As Integer = rectBar.X + 14
            Dim y As Integer = rectBar.Y + 3

            Select Case m_visualStyle
                Case VisualStyles.Mozilla

                    For i As Integer = 0 To 29
                        ' light dot
                        g.DrawLine(New Pen(SystemColors.ControlLightLight), x + (i * 3), y, x + 1 + (i * 3), y + 1)
                        ' dark dot
                        g.DrawLine(New Pen(SystemColors.ControlDarkDark), x + 1 + (i * 3), y + 1, x + 2 + (i * 3), y + 2)
                        ' overdraw the background color as we actually drew 2px diagonal lines, not just dots
                        If hot Then
                            g.DrawLine(New Pen(hotColor), x + 1 + (i * 3), y + 2, x + 2 + (i * 3), y + 2)
                        Else
                            g.DrawLine(New Pen(Me.BackColor), x + 1 + (i * 3), y + 2, x + 2 + (i * 3), y + 2)
                        End If
                    Next
                    Exit Select

                Case VisualStyles.DoubleDots

                    For i As Integer = 0 To 29
                        ' light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLightLight), x + 1 + (i * 3), y, 1, 1)
                        ' dark dot
                        g.DrawRectangle(New Pen(SystemColors.ControlDark), x + (i * 3), y - 1, 1, 1)
                        i += 1
                        ' light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLightLight), x + 1 + (i * 3), y + 2, 1, 1)
                        ' dark dot
                        g.DrawRectangle(New Pen(SystemColors.ControlDark), x + (i * 3), y + 1, 1, 1)
                    Next
                    Exit Select

                Case VisualStyles.Win9x

                    g.DrawLine(New Pen(SystemColors.ControlLightLight), x, y, x, y + 2)
                    g.DrawLine(New Pen(SystemColors.ControlLightLight), x, y, x + 88, y)
                    g.DrawLine(New Pen(SystemColors.ControlDark), x, y + 2, x + 88, y + 2)
                    g.DrawLine(New Pen(SystemColors.ControlDark), x + 88, y, x + 88, y + 2)
                    Exit Select

                Case VisualStyles.XP

                    For i As Integer = 0 To 17
                        ' light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLight), x + (i * 5), y, 2, 2)
                        ' light light dot
                        g.DrawRectangle(New Pen(SystemColors.ControlLightLight), x + 1 + (i * 5), y + 1, 1, 1)
                        ' dark dark dot
                        g.DrawRectangle(New Pen(SystemColors.ControlDarkDark), x + (i * 5), y, 1, 1)
                        ' dark fill
                        g.DrawLine(New Pen(SystemColors.ControlDark), x + (i * 5), y, x + (i * 5) + 1, y)
                        g.DrawLine(New Pen(SystemColors.ControlDark), x + (i * 5), y, x + (i * 5), y + 1)
                    Next
                    Exit Select

                Case VisualStyles.Lines

                    For i As Integer = 0 To 43
                        g.DrawLine(New Pen(SystemColors.ControlDark), x + (i * 2), y, x + (i * 2), y + 2)
                    Next

                    Exit Select
            End Select

        End If
    End Sub

    Private Sub CollapsibleSplitter_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
        If (hot Or currentState = SplitterState.Collapsed) AndAlso currentState <> SplitterState.Collapsing AndAlso currentState <> SplitterState.Expanding Then
            ToggleSplitter()
        End If
    End Sub

    Private Sub CollapsibleSplitter_EnabledChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.EnabledChanged
        Me.Invalidate()
    End Sub

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
        If Not Me.hot AndAlso Not Me.IsCollapsed Then
            MyBase.OnMouseDown(e)
        End If
    End Sub

    Private Sub CollapsibleSplitter_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseLeave
        hot = False
        Me.Cursor = Cursors.Default
        Me.Invalidate()
    End Sub

    Private Sub CollapsibleSplitter_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
        ' check to see if the mouse cursor position is within the bounds of our control

        If rectBar.Contains(e.X, e.Y) OrElse (Me.currentState = SplitterState.Collapsed AndAlso SplitterRectangle.Contains(e.X, e.Y)) Then
            If Not Me.hot Then
                Me.hot = True
                Me.Cursor = Cursors.Hand
                Me.Invalidate()
            End If
        Else
            If Me.hot Then
                Me.hot = False
                Me.Invalidate()
            End If

            Me.Cursor = Cursors.Default

            If Not Me.IsCollapsed Then
                Me.Cursor = Cursors.Default
            Else
                If Me.Orientation = Windows.Forms.Orientation.Vertical Then
                    Me.Cursor = Cursors.VSplit
                Else
                    Me.Cursor = Cursors.HSplit
                End If
            End If
        End If

    End Sub

    Private Sub CollapsibleSplitter_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
        Me.Invalidate()
    End Sub

    Private Sub CollapsibleSplitter_SplitterMoved(ByVal sender As Object, ByVal e As System.Windows.Forms.SplitterEventArgs) Handles Me.SplitterMoved
        If Me.SplitterDistance <> Me.CollapsedDistance And Me.currentState = SplitterState.Expanded Then m_ExpandedDistance = Me.SplitterDistance
    End Sub

#End Region

End Class

